﻿using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Signers;
using System;
using System.Text;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Asn1.GM;

namespace Yt.Core.Security.SM
{
   /// <summary>
   /// 国密SM2算法（类似RSA）
   /// </summary>
    public class SM2
    {
        #region Private
        class HexUtils
        {
            private const string pattern = "^[a-f0-9]+$";

            public static bool isHex(string value)
            {
                if (null == value)
                {
                    return false;
                }
                return System.Text.RegularExpressions.Regex.IsMatch(value, pattern);
            }

            public static byte[] decode(String str)
            {
                if (null == str)
                {
                    return null;
                }
                if (isHex(str))
                {
                    return hexStrToByte(str);
                }
                return Org.BouncyCastle.Utilities.Encoders.Base64.Decode(str);
            }

            public static byte[] hexStrToByte(String hexStr)
            {
                if ((null == hexStr) || (hexStr.Length == 0))
                {
                    return null;
                }
                char[] hexData = hexStr.ToCharArray();
                int len = hexData.Length;
                if ((len & 0x1) != 0)
                {
                    throw new SystemException("Odd number of characters.");
                }
                byte[] out1 = new byte[len >> 1];

                int i = 0;
                for (int j = 0; j < len; i++)
                {
                    int f = toDigit(hexData[j], j) << 4;
                    j++;
                    f |= toDigit(hexData[j], j);
                    j++;
                    out1[i] = ((byte)(f & 0xFF));
                }
                return out1;
            }

            private static int toDigit(char ch, int index)
            {
                int digit = Convert.ToInt32(ch.ToString(), 16);
                //int digit = Character.digit(ch, 16);
                if (digit == -1)
                {
                    throw new SystemException("Illegal hexadecimal character " + ch + " at index " + index);
                }
                return digit;
            }
        }
        class PrivateSM2
        {
            private ECPrivateKeyParameters privateKeyParameters;
            private ECPublicKeyParameters publicKeyParameters;

            private SM2Param sm2Param;

            private static X9ECParameters x9ECParameters = GMNamedCurves.GetByName("sm2p256v1");

            public PrivateSM2(string priKey, string pubKey)
            {
                this.init(HexUtils.decode(priKey), HexUtils.decode(pubKey));
            }

            private void init(byte[] priKey, byte[] pubKey)
            {
                this.sm2Param = new SM2Param();
                if (null != priKey && this.privateKeyParameters == null)
                {
                    this.privateKeyParameters = new ECPrivateKeyParameters(new Org.BouncyCastle.Math.BigInteger(1, priKey), this.sm2Param.ecc_bc_spec);
                }
                if (null != pubKey && this.publicKeyParameters == null)
                {
                    this.publicKeyParameters = new ECPublicKeyParameters(this.sm2Param.ecc_curve.DecodePoint(pubKey), this.sm2Param.ecc_bc_spec);

                }
            }


            /**
             * 加签
             */
            public string Sign(string data)
            {
                byte[] msg = Encoding.UTF8.GetBytes(data);
                SM2Signer sm2Signer = new SM2Signer();
                sm2Signer.Init(true, this.privateKeyParameters);
                sm2Signer.BlockUpdate(msg, 0, msg.Length);
                return Hex.ToHexString(sm2Signer.GenerateSignature());
            }


            /*
             * 验签
             */
            public bool verifySign(string data, string sign)
            {
                byte[] signHex = HexUtils.hexStrToByte(sign);
                byte[] msgByte = Encoding.UTF8.GetBytes(data);
                SM2Signer sm2Signer = new SM2Signer();
                sm2Signer.Init(false, this.publicKeyParameters);
                sm2Signer.BlockUpdate(msgByte, 0, msgByte.Length);
                return sm2Signer.VerifySignature(signHex);
            }

            /**
             * 加密
             * 
             */
            public byte[] encrypt(byte[] plainText)
            {
                SM2Engine engine = new SM2Engine();
                engine.Init(true, new ParametersWithRandom(this.publicKeyParameters));
                return ChangeC1C2C3ToC1C3C2(engine.ProcessBlock(plainText, 0, plainText.Length));
            }

            /**
            * bc加解密使用旧标c1||c2||c3，此方法在加密后调用，将结果转化为c1||c3||c2
            * @param c1c2c3
            * @return
            */
            private static byte[] ChangeC1C2C3ToC1C3C2(byte[] c1c2c3)
            {
                int c1Len = (x9ECParameters.Curve.FieldSize + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
                const int c3Len = 32; //new SM3Digest().getDigestSize();
                byte[] result = new byte[c1c2c3.Length];
                Buffer.BlockCopy(c1c2c3, 0, result, 0, c1Len); //c1
                Buffer.BlockCopy(c1c2c3, c1c2c3.Length - c3Len, result, c1Len, c3Len); //c3
                Buffer.BlockCopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.Length - c1Len - c3Len); //c2
                return result;
            }

            /**
             * 
             * 解密
             * 
             */
            public byte[] deceypt(byte[] plainText)
            {
                byte[] plain = ChangeC1C3C2ToC1C2C3(plainText);
                SM2Engine engine = new SM2Engine();
                engine.Init(false, this.privateKeyParameters);
                return engine.ProcessBlock(plain, 0, plain.Length);
            }

            /**
            * bc加解密使用旧标c1||c3||c2，此方法在解密前调用，将密文转化为c1||c2||c3再去解密
            * @param c1c3c2
            * @return
            */
            private static byte[] ChangeC1C3C2ToC1C2C3(byte[] c1c3c2)
            {
                int c1Len = (x9ECParameters.Curve.FieldSize + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
                const int c3Len = 32; //new SM3Digest().GetDigestSize();
                byte[] result = new byte[c1c3c2.Length];
                Buffer.BlockCopy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65
                Buffer.BlockCopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.Length - c1Len - c3Len); //c2
                Buffer.BlockCopy(c1c3c2, c1Len, result, c1c3c2.Length - c3Len, c3Len); //c3
                return result;
            }

            /// <summary>
            /// 生成SM2秘钥对
            /// </summary>
            /// <returns>Keys对象</returns>
            public static Keys GenerateKeys()
            {
                AsymmetricCipherKeyPair kPair = genCipherKeyPair();
                ECPrivateKeyParameters ecPrivateKey = (ECPrivateKeyParameters)kPair.Private;
                ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters)kPair.Public;
                Org.BouncyCastle.Math.BigInteger priKey = ecPrivateKey.D;
                ECPoint pubKey = ecPublicKey.Q;
                byte[] priByte = priKey.ToByteArray();
                byte[] pubByte = pubKey.GetEncoded(false);
                if (priByte.Length == 33)
                {
                    byte[] newPriByte = new byte[32];
                    Array.Copy(priByte, 1, newPriByte, 0, 32);
                    priByte = newPriByte;
                }
                return new Keys(Hex.ToHexString(pubByte), Hex.ToHexString(priByte));  
            }
            /**
             *  生成引用
             * */
            private static AsymmetricCipherKeyPair genCipherKeyPair()
            {
                SM2Param ecc_param = new SM2Param();
                ECDomainParameters ecDomainParamters = ecc_param.ecc_bc_spec;
                ECKeyGenerationParameters ecGenParam = new ECKeyGenerationParameters(ecDomainParamters, new SecureRandom());

                ECKeyPairGenerator ecKeyPairGenerator = new ECKeyPairGenerator();
                ecKeyPairGenerator.Init(ecGenParam);
                return ecKeyPairGenerator.GenerateKeyPair();
            }

            private class SM2Param
            {
                public static String[] ecc_param = { "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0" };
                public Org.BouncyCastle.Math.BigInteger ecc_p;
                public Org.BouncyCastle.Math.BigInteger ecc_a;
                public Org.BouncyCastle.Math.BigInteger ecc_b;
                public Org.BouncyCastle.Math.BigInteger ecc_n;
                public Org.BouncyCastle.Math.BigInteger ecc_gx;
                public Org.BouncyCastle.Math.BigInteger ecc_gy;
                public ECCurve ecc_curve;
                public ECDomainParameters ecc_bc_spec;

                public SM2Param()
                {
                    this.ecc_p = new Org.BouncyCastle.Math.BigInteger(ecc_param[0], 16);
                    this.ecc_a = new Org.BouncyCastle.Math.BigInteger(ecc_param[1], 16);
                    this.ecc_b = new Org.BouncyCastle.Math.BigInteger(ecc_param[2], 16);
                    this.ecc_n = new Org.BouncyCastle.Math.BigInteger(ecc_param[3], 16);
                    this.ecc_gx = new Org.BouncyCastle.Math.BigInteger(ecc_param[4], 16);
                    this.ecc_gy = new Org.BouncyCastle.Math.BigInteger(ecc_param[5], 16);
                    this.ecc_curve = new FpCurve(ecc_p, ecc_a, ecc_b, ecc_n, Org.BouncyCastle.Math.BigInteger.One);
                    this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_curve.CreatePoint(this.ecc_gx, this.ecc_gy), this.ecc_n);
                }
            }
        }
        #endregion

        #region public
        /// <summary>
        /// 生成SM2秘钥对
        /// </summary>
        /// <returns>Keys对象</returns>
        public static Keys GenerateKeys()
        {
            return PrivateSM2.GenerateKeys();
        }
        /// <summary>
        /// SM2签名
        /// </summary>
        /// <param name="data">签名的数据</param>
        /// <param name="priKey">私钥</param>
        /// <returns>签名结果</returns>
        public static string Sign(string data, string priKey)
        {
            PrivateSM2 sm2 = new PrivateSM2(priKey, null);
            return sm2.Sign(data);

        }

        /// <summary>
        ///  SM2验签
        /// </summary>
        /// <param name="msg">源数据</param>
        /// <param name="pubKey">公钥</param>
        /// <param name="sign">签名的数据</param>
        /// <returns>验签结果</returns>
        public static bool verifySign(string msg, string pubKey, string sign)
        {
            PrivateSM2 sm2 = new PrivateSM2(null, pubKey);
            return sm2.verifySign(msg, sign);
        }

        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="plainText">要加密的文本</param>
        /// <param name="pubKey">公钥</param>
        /// <returns>加密的Base64字符串</returns>
        public static string encryptBase64(string plainText, string pubKey)
        {
            if (string.IsNullOrEmpty(plainText)) return "";
            PrivateSM2 sm2 = new PrivateSM2(null, pubKey);
            byte[] encryptByte = sm2.encrypt(Encoding.UTF8.GetBytes(plainText));
            return Base64.ToBase64String(encryptByte);
        }
        /// <summary>
        /// 解密
        /// </summary>
        /// <param name="plainText">要解密的Base64文本</param>
        /// <param name="priKey">私钥</param>
        /// <returns>解密的文本</returns>
        public static string decryptBase64(string plainText, string priKey)
        {
            try
            {
                PrivateSM2 sm2 = new PrivateSM2(priKey, null);
                byte[] deCode = Base64.Decode(plainText);
                byte[] decryptText = sm2.deceypt(deCode);
                return Encoding.UTF8.GetString(decryptText);
            }
            catch
            {
                return "";
            }

        }
        #endregion
    }
}
